home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Resources / Audio, Video & Photo / Songbird 0.7.0 / Songbird_0.7.0_windows-i686-msvc8.exe / components / sbLibraryServicePaneService.js < prev    next >
Text File  |  2008-08-12  |  64KB  |  1,940 lines

  1. /** vim: ts=2 sw=2 expandtab
  2. //
  3. // BEGIN SONGBIRD GPL
  4. //
  5. // This file is part of the Songbird web player.
  6. //
  7. // Copyright(c) 2005-2008 POTI, Inc.
  8. // http://songbirdnest.com
  9. //
  10. // This file may be licensed under the terms of of the
  11. // GNU General Public License Version 2 (the "GPL").
  12. //
  13. // Software distributed under the License is distributed
  14. // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
  15. // express or implied. See the GPL for the specific language
  16. // governing rights and limitations.
  17. //
  18. // You should have received a copy of the GPL along with this
  19. // program. If not, go to http://www.gnu.org/licenses/gpl.html
  20. // or write to the Free Software Foundation, Inc.,
  21. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. //
  23. // END SONGBIRD GPL
  24. //
  25.  */
  26.  
  27. /**
  28.  * \file sbLibraryServicePane.js
  29.  */
  30.  
  31. const Cc = Components.classes;
  32. const Ci = Components.interfaces;
  33. const Cr = Components.results;
  34.  
  35. Components.utils.import("resource://app/jsmodules/sbProperties.jsm");
  36. Components.utils.import("resource://app/jsmodules/sbLibraryUtils.jsm");
  37. Components.utils.import("resource://app/jsmodules/DropHelper.jsm");
  38. Components.utils.import("resource://app/jsmodules/StringUtils.jsm");
  39. Components.utils.import("resource://app/jsmodules/WindowUtils.jsm");
  40.  
  41. const CONTRACTID = "@songbirdnest.com/servicepane/library;1";
  42. const ROOTNODE = "SB:Bookmarks";
  43.  
  44. const URN_PREFIX_ITEM = 'urn:item:';
  45. const URN_PREFIX_LIBRARY = 'urn:library:';
  46.  
  47. const LSP = 'http://songbirdnest.com/rdf/library-servicepane#';
  48. const SP='http://songbirdnest.com/rdf/servicepane#';
  49.  
  50.  
  51. const TYPE_X_SB_TRANSFER_MEDIA_ITEM = "application/x-sb-transfer-media-item";
  52. const TYPE_X_SB_TRANSFER_MEDIA_LIST = "application/x-sb-transfer-media-list";
  53. const TYPE_X_SB_TRANSFER_MEDIA_ITEMS = "application/x-sb-transfer-media-items";
  54.  
  55. /**
  56.  * Given the arguments var of a function, dump the
  57.  * name of the function and the parameters provided
  58.  */
  59. function logcall(parentArgs) {
  60.   dump("\n");
  61.   dump(parentArgs.callee.name + "(");
  62.   for (var i = 0; i < parentArgs.length; i++) {
  63.     dump(parentArgs[i]);
  64.     if (i < parentArgs.length - 1) {
  65.       dump(', ');
  66.     }
  67.   }
  68.   dump(")\n");
  69. }
  70.  
  71.  
  72.  
  73. /**
  74.  * /class sbLibraryServicePane
  75.  * /brief Provides library related nodes for the service pane
  76.  * \sa sbIServicePaneService sbILibraryServicePaneService
  77.  */
  78. function sbLibraryServicePane() {
  79.   this._servicePane = null;
  80.   this._libraryManager = null;
  81.   this._lastShortcuts = null;
  82.   this._lastMenuitems = null;
  83.  
  84.   // use the default stringbundle to translate tree nodes
  85.   this.stringbundle = null;
  86.  
  87.   this._batch = new LibraryUtils.BatchHelper();
  88.   this._refreshPending = false;
  89. }
  90. sbLibraryServicePane.prototype.QueryInterface =
  91. function sbLibraryServicePane_QueryInterface(iid) {
  92.   if (!iid.equals(Ci.nsISupports) &&
  93.     !iid.equals(Ci.nsIObserver) &&
  94.     !iid.equals(Ci.sbIServicePaneModule) &&
  95.     !iid.equals(Ci.sbILibraryServicePaneService) &&
  96.     !iid.equals(Ci.sbILibraryManagerListener) &&
  97.     !iid.equals(Ci.sbIMediaListListener)) {
  98.     throw Components.results.NS_ERROR_NO_INTERFACE;
  99.   }
  100.   return this;
  101. }
  102.  
  103.  
  104. //////////////////////////
  105. // sbIServicePaneModule //
  106. //////////////////////////
  107.  
  108. sbLibraryServicePane.prototype.servicePaneInit =
  109. function sbLibraryServicePane_servicePaneInit(sps) {
  110.   //logcall(arguments);
  111.  
  112.   // keep track of the service pane service
  113.   this._servicePane = sps;
  114.  
  115.   // Before initializing, hide all existing library nodes.
  116.   // This way we only show things when we are notified
  117.   // that they actually exist.
  118.   this._hideLibraryNodes(this._servicePane.root);
  119.  
  120.   // register for notification that the library manager is initialized
  121.   var obs = Cc["@mozilla.org/observer-service;1"].
  122.             getService(Ci.nsIObserverService);
  123.   obs.addObserver(this, "songbird-library-manager-ready", false);
  124.   obs.addObserver(this, "songbird-library-manager-before-shutdown", false);
  125.  
  126.   // get the library manager
  127.   this._initLibraryManager();
  128. }
  129.  
  130. sbLibraryServicePane.prototype.shutdown =
  131. function sbLibraryServicePane_shutdown() {
  132.   this._removeAllLibraries();
  133. }
  134.  
  135. sbLibraryServicePane.prototype.fillContextMenu =
  136. function sbLibraryServicePane_fillContextMenu(aNode, aContextMenu, aParentWindow) {
  137.  
  138.   // the playlists folder and the local library node get the "New Foo..." items
  139.   if (aNode.id == 'SB:Playlists' ||
  140.       aNode.getAttributeNS(LSP, 'ListCustomType') == 'local') {
  141.     this.fillNewItemMenu(aNode, aContextMenu, aParentWindow);
  142.   }
  143.  
  144.   var list = this.getLibraryResourceForNode(aNode);
  145.   if (list) {
  146.     this._appendCommands(aContextMenu, list, aParentWindow);
  147.  
  148.     // Add menu items for a smart media list
  149.     if (list instanceof Ci.sbILocalDatabaseSmartMediaList) {
  150.       if (list.userEditable) {
  151.         this._appendMenuItem(aContextMenu, SBString("command.smartpl.properties"), function(event) {
  152.           var watcher = Cc["@mozilla.org/embedcomp/window-watcher;1"]
  153.                           .getService(Ci.nsIWindowWatcher);
  154.           watcher.openWindow(aParentWindow,
  155.                             "chrome://songbird/content/xul/smartPlaylist.xul",
  156.                             "_blank",
  157.                             "chrome,dialog=yes,centerscreen,modal,titlebar=no",
  158.                             list);
  159.         });
  160.       }
  161.     }
  162.  
  163.     // Add menu items for a dynamic media list
  164.     if (list.getProperty("http://songbirdnest.com/data/1.0#isSubscription") == "1") {
  165.       this._appendMenuItem(aContextMenu, SBString("command.subscription.properties"), function(event) {
  166.         var params = Cc["@songbirdnest.com/moz/xpcom/threadsafe-array;1"].createInstance(Ci.nsIMutableArray);
  167.         params.appendElement(list, false);
  168.  
  169.         WindowUtils.openModalDialog(null,
  170.                                     "chrome://songbird/content/xul/subscribe.xul",
  171.                                     "",
  172.                                     "chrome,modal=yes,centerscreen",
  173.                                     params,
  174.                                     null);
  175.       });
  176.       this._appendMenuItem(aContextMenu, "Update", function(event) { //XXX todo: localize
  177.         var dps = Cc["@songbirdnest.com/Songbird/Library/LocalDatabase/DynamicPlaylistService;1"]
  178.                     .getService(Ci.sbILocalDatabaseDynamicPlaylistService);
  179.         dps.updateNow(list);
  180.       });
  181.     }
  182.   }
  183. }
  184.  
  185. sbLibraryServicePane.prototype.fillNewItemMenu =
  186. function sbLibraryServicePane_fillNewItemMenu(aNode, aContextMenu, aParentWindow) {
  187.   var sbSvc = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
  188.   var stringBundle = sbSvc.createBundle("chrome://songbird/locale/songbird.properties");
  189.  
  190.   function add(id, label, accesskey, oncommand) {
  191.     var menuitem = aContextMenu.ownerDocument.createElement('menuitem');
  192.     menuitem.setAttribute('id', id);
  193.     menuitem.setAttribute('class', 'menuitem-iconic');
  194.     menuitem.setAttribute('label', stringBundle.GetStringFromName(label));
  195.     menuitem.setAttribute('accesskey', stringBundle.GetStringFromName(accesskey));
  196.     menuitem.setAttribute('oncommand', oncommand);
  197.     aContextMenu.appendChild(menuitem);
  198.   }
  199.  
  200.   add('menuitem_file_new', 'menu.file.new', 'menu.file.new.accesskey', 'doMenu("menuitem_file_new")');
  201.   add('file.smart', 'menu.file.smart', 'menu.file.smart.accesskey', 'doMenu("menuitem_file_smart")');
  202.   add('menuitem_file_remote', 'menu.file.remote', 'menu.file.remote.accesskey', 'doMenu("menuitem_file_remote")');
  203. }
  204.  
  205. sbLibraryServicePane.prototype.onSelectionChanged =
  206. function sbLibraryServicePane_onSelectionChanged(aNode, aContainer, aParentWindow) {
  207.   this._destroyShortcuts(aContainer, aParentWindow);
  208.   var list;
  209.   if (aNode) list = this.getLibraryResourceForNode(aNode);
  210.   if (list) this._createShortcuts(list, aContainer, aParentWindow);
  211. }
  212.  
  213. sbLibraryServicePane.prototype._createShortcuts =
  214. function sbLibraryServicePane__createShortcuts(aList, aContainer, aWindow) {
  215.   var shortcuts = aWindow.document.createElement("sb-commands-shortcuts");
  216.   shortcuts.setAttribute("id", "playlist-commands-shortcuts");
  217.   shortcuts.setAttribute("commandtype", "medialist");
  218.   shortcuts.setAttribute("bind", aList.library.guid + ';' + aList.guid);
  219.   aContainer.appendChild(shortcuts);
  220.   this._lastShortcuts = shortcuts;
  221. }
  222.  
  223. sbLibraryServicePane.prototype._destroyShortcuts =
  224. function sbLibraryServicePane__destroyShortcuts(aContainer, aWindow) {
  225.   if (this._lastShortcuts) {
  226.     this._lastShortcuts.destroy();
  227.     aContainer.removeChild(this._lastShortcuts);
  228.     this._lastShortcuts = null;
  229.   }
  230. }
  231.  
  232. sbLibraryServicePane.prototype._canDownloadDrop =
  233. function sbLibraryServicePane__canDownloadDrop(aDragSession) {
  234.   // bail out if the drag session does not contain internal items.
  235.   // We may eventually add a handler to add an external media URL drop on 
  236.   // the download playlist.
  237.   if (!InternalDropHandler.isSupported(aDragSession)) 
  238.     return false;
  239.     
  240.   var IOS = Cc["@mozilla.org/network/io-service;1"]
  241.               .getService(Ci.nsIIOService);
  242.  
  243.   function canDownload(aMediaItem) {
  244.     var contentSpec = aMediaItem.getProperty(SBProperties.contentURL);
  245.     var contentURL = IOS.newURI(contentSpec, null, null);
  246.     switch(contentURL.scheme) {
  247.       case "http":
  248.       case "https":
  249.       case "ftp":
  250.         // these are safe to download
  251.         return true;
  252.     }
  253.     return false;
  254.   }
  255.  
  256.   if (aDragSession.isDataFlavorSupported(TYPE_X_SB_TRANSFER_MEDIA_ITEMS)) {
  257.     var context = DNDUtils.
  258.       getInternalTransferDataForFlavour(aDragSession,
  259.                                         TYPE_X_SB_TRANSFER_MEDIA_ITEMS,
  260.                                         Ci.sbIMediaItemsTransferContext);
  261.     var items = context.items;
  262.     // we must remember to reset the context before we exit, so that when we
  263.     // actually need the items in onDrop we can get them again!
  264.     var count = 0;    
  265.     var downloadable = true;
  266.     while (items.hasMoreElements() && downloadable) {
  267.       downloadable = canDownload(items.getNext());
  268.       ++count;
  269.     }
  270.     
  271.     // we can't download nothing.
  272.     if (count == 0) { downloadable = false; }
  273.     
  274.     // rewind the items list.
  275.     context.reset();
  276.     
  277.     return downloadable;
  278.   } else {
  279.     Components.utils.reportError("_getMediaListForDrop should have returned null");
  280.     return false;
  281.   }
  282. }
  283.  
  284. sbLibraryServicePane.prototype._getMediaListForDrop =
  285. function sbLibraryServicePane__getMediaListForDrop(aNode, aDragSession, aOrientation) {
  286.   // work out what the drop would target and return an sbIMediaList to
  287.   // represent that target, or null if the drop is not allowed
  288.  
  289.   // check if we support this drop at all
  290.   if (!InternalDropHandler.isSupported(aDragSession)&& 
  291.       !ExternalDropHandler.isSupported(aDragSession)) {
  292.     return null;
  293.   }
  294.  
  295.   // are we dropping a list ?
  296.   var dropList = aDragSession.isDataFlavorSupported(TYPE_X_SB_TRANSFER_MEDIA_LIST);
  297.  
  298.   // work out where the drop items are going
  299.   var targetNode = aNode;
  300.   var dropOnto = true;
  301.   if (aOrientation != 0) {
  302.     dropOnto = false;
  303.   }
  304.  
  305.   // work out what library resource is associated with the target node 
  306.   var targetResource = this.getLibraryResourceForNode(targetNode);
  307.  
  308.   // check that the target list or library accepts drops
  309.   // TODO, i can't believe we don't have a property to check for that :/
  310.   // I'm told that the transfer policy will solve this, this might be a good
  311.   // spot for querying it in the future... (?)
  312.  
  313.   // is the target a library
  314.   var targetIsLibrary = (targetResource instanceof Ci.sbILibrary);
  315.  
  316.   // The Rules:
  317.   //  * non-playlist items can be dropped on top of playlists and libraries
  318.   //  * playlist items can be dropped on top of other libraries than their own
  319.   //  * playlists can be dropped next to other playlists, either as part of a
  320.   //      reordering, or as a playlist transfer to a different library, with a 
  321.   //      specific position to drop to
  322.  
  323.   if (dropOnto) {
  324.     if (!dropList) {
  325.       // we are dropping onto a target node, and this is not a playlist, 
  326.       // so return the target medialist that corresponds to the target node. 
  327.       return targetResource;
  328.     } else {
  329.       // we are dropping a list onto a target node, we can only accept this drop
  330.       // on a library, so if the target isnt one, refuse it
  331.       // note: if we ever want to support dropping playlists on playlists, this
  332.       // is the sport to add the new case
  333.       if (!targetIsLibrary) {
  334.         return null;
  335.       }
  336.         
  337.       // we can only accept a list drop on a library different than its own 
  338.       // (because its own library already has all of its tracks, so it makes no
  339.       // sense to allow it), so extract the medialist that we are dropping, 
  340.       // and check where it comes from
  341.       var draggedList = DNDUtils.
  342.         getInternalTransferDataForFlavour(aDragSession,
  343.                                           TYPE_X_SB_TRANSFER_MEDIA_LIST,
  344.                                           Ci.sbIMediaListTransferContext);
  345.       if (targetResource == draggedList.library) {
  346.         return null;
  347.       }
  348.         
  349.       // we are indeed dropping the playlist onto a different library, accept
  350.       // the drop
  351.       return targetResource;
  352.     }
  353.  
  354.   } else {
  355.  
  356.     // we are dropping in between two items
  357.     if (dropList) {
  358.       
  359.       // we are dropping a playlist.
  360.       
  361.       // if we are trying to insert the playlist between two toplevel nodes,
  362.       // refuse the drop
  363.       if (targetNode.parentNode.id == "SB:Root") {
  364.         return null;
  365.       }
  366.       
  367.       // we now know that this is either a reorder inside a single container 
  368.       // or a playlist transfer from one library to another, involving two
  369.       // different containers. 
  370.       
  371.       // to be able to discriminate between those two cases, we need to know
  372.       // where the playlist is going, as well as where it comes from. 
  373.       
  374.       // find where the playlist comes from
  375.       var draggedList = DNDUtils.
  376.         getInternalTransferDataForFlavour(aDragSession,
  377.                                           TYPE_X_SB_TRANSFER_MEDIA_LIST,
  378.                                           Ci.sbIMediaListTransferContext);
  379.       var fromResource = draggedList.library;
  380.       
  381.       var toResource = null;
  382.  
  383.       // finding where the playlist is going however is much more complicated,
  384.       // because we cannot rely on the fact that we can extract a library
  385.       // resource from the parent container for the target list: we could be
  386.       // dropping into a foreign container (eg. a device node), and the actual
  387.       // library node could be a sibling of the target node.
  388.       
  389.       // so we'll first try to get a resource from the parent node, in case we
  390.       // are dropping into a list of playlist that are children of their
  391.       // library
  392.       var parentNode = targetNode.parentNode;
  393.       if (parentNode) {
  394.         toResource = this.getLibraryResourceForNode(parentNode);
  395.       }
  396.       
  397.       // ... and if there was no parent for the target node, or the parent had 
  398.       // no library resource, we can still look around the drop target for 
  399.       // libraries and playlists nodes. if we find any of these, we can assume 
  400.       // that the resource targeted for drop is these item's library
  401.       
  402.       if (!toResource) {
  403.         var siblingNode = targetNode.previousSibling;
  404.         if (siblingNode) {
  405.           toResource = this.getLibraryResourceForNode(siblingNode);
  406.         }
  407.       }
  408.       if (!toResource) {
  409.         var siblingNode = targetNode.nextSibling;
  410.         if (siblingNode) {
  411.           toResource = this.getLibraryResourceForNode(siblingNode);
  412.         }
  413.       }
  414.         
  415.       // if we have not found what the destination library is, refuse the drop
  416.       if (!toResource) 
  417.         return null;
  418.         
  419.       // get the library for the resource we found
  420.       toResource = toResource.library;
  421.         
  422.       // if the source and destination library are the same, this is a reorder,
  423.       // we can actually pretend to refuse it, because the servicePaneService
  424.       // is going to accept it based on further rules (dndAcceptIn, dndAcceptNear)
  425.       // and because there is no actual drop handling to be performed, the nodes
  426.       // will be reordered in the tree and that is it.
  427.       if (toResource == fromResource) 
  428.         return null;
  429.       
  430.       // otherwise, this is a playlist transfer from one library to another,
  431.       // we should accept the drop, but at the condition that we are not
  432.       // dropping above a library, because we want to keep playlists either 
  433.       // below or as children of their library nodes. 
  434.       if (targetIsLibrary && aOrientation == 1) 
  435.         return null;
  436.       
  437.       // the destination seems correct, accept the drop, the handler will
  438.       // first copy the list to the new library, and then move the node
  439.       // to where it belongs  
  440.       return toResource;
  441.     }
  442.   }
  443.   
  444.   // default is refuse the drop
  445.   return null;
  446. }
  447.  
  448. sbLibraryServicePane.prototype.canDrop =
  449. function sbLibraryServicePane_canDrop(aNode, aDragSession, aOrientation, aWindow) {
  450.   // don't allow drop on read-only nodes
  451.   if (aNode.getAttributeNS(LSP, "ReadOnly") == "true")
  452.     return false;
  453.  
  454.   var list = this._getMediaListForDrop(aNode, aDragSession, aOrientation);
  455.   if (list) {
  456.     
  457.     // check if the list is in a readonly library
  458.     if (!list.library.userEditable ||
  459.         !list.library.userEditableContent) {
  460.       // this is a list for a readonly library, can't drop
  461.       return false;
  462.     }
  463.     
  464.     // check if the list is itself readonly
  465.     if (!list.userEditable ||
  466.         !list.userEditableContent) {
  467.       // this list content is readonly, can't drop
  468.       return false;
  469.     }
  470.     
  471.     // XXX Mook: hack for bug 4760 to do special handling for the download
  472.     // playlist.  This will need to be expanded later to use IDLs on the
  473.     // list so that things like extensions can do this too.
  474.     var customType = list.getProperty(SBProperties.customType);
  475.     if (customType == "download") {
  476.       return this._canDownloadDrop(aDragSession);
  477.     }
  478.     // test whether the drop contains supported flavours
  479.     return InternalDropHandler.isSupported(aDragSession) ||
  480.            ExternalDropHandler.isSupported(aDragSession);
  481.   } else {
  482.     return false;
  483.   }
  484. }
  485.  
  486. sbLibraryServicePane.prototype.onDrop =
  487. function sbLibraryServicePane_onDrop(aNode, aDragSession, aOrientation, aWindow) {
  488.   // don't allow drop on read-only nodes
  489.   if (aNode.getAttributeNS(LSP, "ReadOnly") == "true")
  490.     return;
  491.  
  492.   // where are we dropping?
  493.   var targetList = this._getMediaListForDrop(aNode, aDragSession, aOrientation);
  494.  
  495.   if (!targetList) {
  496.     // don't know how to drop here
  497.     return;
  498.   }
  499.   
  500.   // perform this test now because the incoming new node makes it unreliable
  501.   // to do in the onCopyMediaList callback
  502.   var isLastSibling = (aNode.nextSibling == null);
  503.   
  504.   var dropHandlerListener = {
  505.     libSPS: this,
  506.     onDropComplete: function(aTargetList,
  507.                              aImportedInLibrary,
  508.                              aDuplicates,
  509.                              aInsertedInMediaList,
  510.                              aOtherDropsHandled) { 
  511.       // show the standard report on the status bar
  512.       return true; 
  513.     },
  514.     onFirstMediaItem: function(aTargetList, aFirstMediaItem) {},
  515.     onCopyMediaList: function(aSourceList, aNewList) {
  516.       // find the node that was created
  517.       var newnode = 
  518.         this.libSPS._servicePane.getNode(this.libSPS._itemURN(aNewList));
  519.       // move the item to the right spot
  520.       switch (aOrientation) {
  521.         case -1:
  522.           aNode.parentNode.insertBefore(newnode, aNode);
  523.           break;
  524.         case 1:
  525.           if (!isLastSibling)
  526.             aNode.parentNode.insertBefore(newnode, aNode.nextSibling);
  527.           // else, the node has already been placed at the right spot by
  528.           // the LibraryServicePaneService
  529.           break;
  530.       }
  531.     }
  532.   };
  533.  
  534.   if (InternalDropHandler.isSupported(aDragSession)) {
  535.     // handle drop of internal items
  536.     InternalDropHandler.dropOnList(aWindow, 
  537.                                    aDragSession, 
  538.                                    targetList, 
  539.                                    -1, 
  540.                                    dropHandlerListener);
  541.  
  542.   } else if (ExternalDropHandler.isSupported(aDragSession)) {
  543.   
  544.     // handle drop of external items
  545.     ExternalDropHandler.dropOnList(aWindow, 
  546.                                    aDragSession, 
  547.                                    targetList, 
  548.                                    -1, 
  549.                                    dropHandlerListener);
  550.   }
  551. }
  552.  
  553. sbLibraryServicePane.prototype._nodeIsLibrary =
  554. function sbLibraryServicePane__nodeIsLibrary(aNode) {
  555.   return aNode.getAttributeNS(LSP, "LibraryGUID") ==
  556.       aNode.getAttributeNS(LSP, "ListGUID");
  557. }
  558. sbLibraryServicePane.prototype.onDragGesture =
  559. function sbLibraryServicePane_onDragGesture(aNode, aTransferable) {
  560.   if (this._nodeIsLibrary(aNode)) {
  561.     // a library isn't dragable
  562.     return false;
  563.   }
  564.  
  565.   // get the list and create the source context
  566.   var list = this._getItemForURN(aNode.id);
  567.   var context = {
  568.     source: list.library,
  569.     count: 1,
  570.     list: list,
  571.     QueryInterface: function(iid) {
  572.       if (iid.equals(Components.interfaces.sbIMediaListTransferContext) ||
  573.           iid.equals(Components.interfaces.nsISupports)) {
  574.         return this;
  575.       }
  576.       throw Components.results.NS_NOINTERFACE;
  577.     }
  578.   };
  579.   
  580.   // register the source context
  581.   var dnd = Components.classes['@songbirdnest.com/Songbird/DndSourceTracker;1']
  582.       .getService(Components.interfaces.sbIDndSourceTracker);
  583.   dnd.reset();
  584.   var handle = dnd.registerSource(context);
  585.  
  586.   // attach the source context to the transferable
  587.   aTransferable.addDataFlavor(TYPE_X_SB_TRANSFER_MEDIA_LIST);
  588.   var text = Components.classes["@mozilla.org/supports-string;1"].
  589.      createInstance(Components.interfaces.nsISupportsString);
  590.   text.data = handle;
  591.   aTransferable.setTransferData(TYPE_X_SB_TRANSFER_MEDIA_LIST, text,
  592.                                 text.data.length*2);
  593.  
  594.   return true;
  595. }
  596.  
  597.  
  598. /**
  599.  * Called when the user has attempted to rename a library/medialist node
  600.  */
  601. sbLibraryServicePane.prototype.onRename =
  602. function sbLibraryServicePane_onRename(aNode, aNewName) {
  603.   //logcall(arguments);
  604.   if (aNode && aNewName) {
  605.     var libraryResource = this.getLibraryResourceForNode(aNode);
  606.     libraryResource.name = aNewName;
  607.   }
  608. }
  609.  
  610.  
  611. //////////////////////////////////
  612. // sbILibraryServicePaneService //
  613. //////////////////////////////////
  614.  
  615.  
  616. /* \brief Suggest a library for creating a new media list
  617.  *
  618.  * \param aMediaListType string identifying a media list type, eg "simple"
  619.  * \param aNode A service pane node to provide context for new list creation
  620.  * \return a library, or null if this service can't suggest anything based on
  621.  *         the given context and type.
  622.  */
  623. sbLibraryServicePane.prototype.suggestLibraryForNewList =
  624. function sbLibraryServicePane_suggestLibraryForNewList(aMediaListType, aNode) {
  625.   //logcall(arguments);
  626.  
  627.   // Must provide a media list type
  628.   if (!aMediaListType) {
  629.     throw Components.results.NS_ERROR_INVALID_ARG;
  630.   }
  631.  
  632.   // Make sure we are fully initialized
  633.   if (!this._libraryManager || !this._servicePane) {
  634.     throw Components.results.NS_ERROR_NOT_INITIALIZED;
  635.   }
  636.  
  637.   // if no node was provided, then suggest the main library
  638.   if (!aNode) 
  639.     return this._libraryManager.mainLibrary;
  640.   
  641.   function checkNode(aNode, aLibServicePane) {
  642.     // If this node is visible and belongs to the library
  643.     // service pane service...
  644.     if (aNode.contractid == CONTRACTID && !aNode.hidden) {
  645.       // If this is a playlist and the playlist belongs
  646.       // to a library that supports the given type,
  647.       // then suggest that library
  648.       var mediaItem = aLibServicePane._getItemForURN(aNode.id);
  649.       if (mediaItem && mediaItem instanceof Ci.sbIMediaList &&
  650.           aLibServicePane._doesLibrarySupportListType(mediaItem.library, aMediaListType))
  651.       {
  652.         return mediaItem.library;
  653.       }
  654.  
  655.       // If this is a library that supports the given type,
  656.       // then suggest the library
  657.       var library = aLibServicePane._getLibraryForURN(aNode.id);
  658.       if (library && library instanceof Ci.sbILibrary &&
  659.           aLibServicePane._doesLibrarySupportListType(library, aMediaListType))
  660.       {
  661.         return library;
  662.       }
  663.     }
  664.  
  665.     return null;
  666.   }
  667.   
  668.   // first, check if the given node is useable as a library...
  669.   var lib = checkNode(aNode, this);
  670.   if (lib)
  671.     return lib;
  672.  
  673.   if (aNode.isContainer) {
  674.     // check the children of the node (but not recursively) for a usable library
  675.     for (var child = aNode.firstChild; child; child = child.nextSibling) {
  676.       lib = checkNode(child, this);
  677.       if (lib)
  678.         return lib;
  679.     }
  680.   }
  681.   
  682.   // Move up the tree looking for libraries that support the
  683.   // given media list type.
  684.   aNode = aNode.parentNode;
  685.   while (aNode && aNode != this._servicePane.root) {
  686.  
  687.     lib = checkNode(aNode, this);
  688.     if (lib)
  689.       return lib;
  690.  
  691.     // Move up the tree
  692.     aNode = aNode.parentNode;
  693.   } // end of while
  694.  
  695.   // If the main library supports the given type, then return that
  696.   if (this._doesLibrarySupportListType(this._libraryManager.mainLibrary,
  697.                                        aMediaListType))
  698.   {
  699.     return this._libraryManager.mainLibrary;
  700.   }
  701.  
  702.   // Oh well, out of luck
  703.   return null;
  704. }
  705.  
  706. sbLibraryServicePane.prototype.createNodeForLibrary =
  707. function sbLibraryServicePane_createNodeForLibrary(aLibrary) {
  708.   if(aLibrary instanceof Ci.sbILibrary) {
  709.     return this._libraryAdded(aLibrary);
  710.   }
  711.  
  712.   return null;
  713. }
  714.  
  715. /* \brief Attempt to get a service pane node for the given library resource
  716.  *
  717.  * \param aResource an sbIMediaItem, sbIMediaItem, or sbILibrary
  718.  * \return a service pane node that represents the given resource, if one exists
  719.  */
  720. sbLibraryServicePane.prototype.getNodeForLibraryResource =
  721. function sbLibraryServicePane_getNodeForLibraryResource(aResource) {
  722.   //logcall(arguments);
  723.  
  724.   // Must be initialized
  725.   if (!this._libraryManager || !this._servicePane) {
  726.     throw Components.results.NS_ERROR_NOT_INITIALIZED;
  727.   }
  728.  
  729.   var node;
  730.  
  731.   // If this is a library, make a library node
  732.   if (aResource instanceof Ci.sbILibrary) {
  733.     node = this._servicePane.getNode(this._libraryURN(aResource));
  734.  
  735.   // If this is a mediaitem, make a mediaitem node
  736.   } else if (aResource instanceof Ci.sbIMediaItem) {
  737.     // Check if this is a storage list for an outer list
  738.     var outerListGuid = aResource.getProperty(SBProperties.outerGUID);
  739.     if (outerListGuid) {
  740.       var library = aResource.library;
  741.       var outerList = library.getMediaItem(outerListGuid);
  742.       if (outerList) {
  743.         aResource = outerList;
  744.       }
  745.     }
  746.     // Look for the node
  747.     node = this._servicePane.getNode(this._itemURN(aResource));
  748.   // Else we don't know what to do, so
  749.   // the arg must be invalid
  750.   } else {
  751.     throw Components.results.NS_ERROR_INVALID_ARG;
  752.   }
  753.  
  754.   return node;
  755. }
  756.  
  757.  
  758. /* \brief Attempt to get a library resource for the given service pane node.
  759.  *
  760.  * Note that there is no guarantee that hidden service pane nodes
  761.  * will have corresponding library resources
  762.  *
  763.  * \param aNode
  764.  * \return a sbIMediaItem, sbIMediaItem, sbILibrary, or null
  765.  */
  766. sbLibraryServicePane.prototype.getLibraryResourceForNode =
  767. function sbLibraryServicePane_getLibraryResourceForNode(aNode) {
  768.   //logcall(arguments);
  769.  
  770.   // Must provide a node
  771.   if (!(aNode instanceof Ci.sbIServicePaneNode)) {
  772.     throw Components.results.NS_ERROR_INVALID_ARG;
  773.   }
  774.   // Must be initialized
  775.   if (!this._libraryManager || !this._servicePane) {
  776.     throw Components.results.NS_ERROR_NOT_INITIALIZED;
  777.   }
  778.  
  779.   // If the node does not belong to us, then we aren't
  780.   // going to find a resource
  781.   if (aNode.contractid != CONTRACTID) {
  782.     return null;
  783.   }
  784.  
  785.   // Attempt to get a resource from the id of the given node
  786.   var resource = this._getItemForURN(aNode.id);
  787.   if (!resource) {
  788.     resource = this._getLibraryForURN(aNode.id);
  789.   }
  790.  
  791.   return resource;
  792. }
  793.  
  794.  
  795. /* \brief Set node read-only property.
  796.  *
  797.  * \param aNode Node to set.
  798.  * \param aReadOnly If true, node is read-only.
  799.  */
  800. sbLibraryServicePane.prototype.setNodeReadOnly =
  801. function sbLibraryServicePane_setNodeReadOnly(aNode, aReadOnly) {
  802.   if (aReadOnly) {
  803.     aNode.editable = false;
  804.     aNode.setAttributeNS(LSP, "ReadOnly", "true");
  805.   } else {
  806.     aNode.editable = true;
  807.     aNode.setAttributeNS(LSP, "ReadOnly", "false");
  808.   }
  809. }
  810.  
  811.  
  812. /////////////////////
  813. // Private Methods //
  814. /////////////////////
  815.  
  816.  
  817. /**
  818.  * Return true if the given library supports the given list type
  819.  */
  820. sbLibraryServicePane.prototype._doesLibrarySupportListType =
  821. function sbLibraryServicePane__doesLibrarySupportListType(aLibrary, aListType) {
  822.   //logcall(arguments);
  823.  
  824.   // If the transfer policy indicates read only media lists, the library does
  825.   // not support adding media lists of any type
  826.   // XXXerik less than SUPER HACK to keep new playlists from being added to
  827.   // device libraries.  This uses a hacked up policy system that will be
  828.   // replaced by a real one.
  829.   var transferPolicy = aLibrary.getProperty(SBProperties.transferPolicy);
  830.   if (transferPolicy && transferPolicy.match(/readOnlyMediaLists/)) {
  831.     return false;
  832.   }
  833.  
  834.   // XXXben SUPER HACK to keep new playlists from being added to the web
  835.   //        library. We should really fix this with our policy system.
  836.   if (aLibrary.equals(LibraryUtils.webLibrary)) {
  837.     return false;
  838.   }
  839.  
  840.   var types = aLibrary.mediaListTypes;
  841.   while (types.hasMore()) {
  842.     if(aListType == types.getNext())  {
  843.       return true;
  844.     }
  845.   }
  846.   return false;
  847. }
  848.  
  849.  
  850. /**
  851.  * Hide this node and any nodes below it that belong to
  852.  * the library service pane service
  853.  */
  854. sbLibraryServicePane.prototype._hideLibraryNodes =
  855. function sbLibraryServicePane__hideLibraryNodes(aNode) {
  856.   //logcall(arguments);
  857.  
  858.   // If this is one of our nodes, hide it
  859.   if (aNode.contractid == CONTRACTID) {
  860.     aNode.hidden = true;
  861.   }
  862.  
  863.   // If this is a container, descend into all children
  864.   if (aNode.isContainer) {
  865.     var children = aNode.childNodes;
  866.     while (children.hasMoreElements()) {
  867.       var child = children.getNext().QueryInterface(Ci.sbIServicePaneNode);
  868.       this._hideLibraryNodes(child);
  869.     }
  870.   }
  871. }
  872.  
  873.  
  874. /**
  875.  * Add all registered libraries to the service pane
  876.  */
  877. sbLibraryServicePane.prototype._addAllLibraries =
  878. function sbLibraryServicePane__addAllLibraries() {
  879.   //logcall(arguments);
  880.   var libraries = this._libraryManager.getLibraries();
  881.   while (libraries.hasMoreElements()) {
  882.     var library = libraries.getNext();
  883.     this._libraryAdded(library);
  884.   }
  885. }
  886.  
  887. /**
  888.  * Remove all registered libraries from the service pane
  889.  */
  890. sbLibraryServicePane.prototype._removeAllLibraries =
  891. function sbLibraryServicePane__removeAllLibraries() {
  892.   //logcall(arguments);
  893.   var libraries = this._libraryManager.getLibraries();
  894.   while (libraries.hasMoreElements()) {
  895.     var library = libraries.getNext();
  896.     this._libraryRemoved(library);
  897.   }
  898. }
  899.  
  900. /**
  901. * Add all media lists found in the given library
  902.  */
  903. sbLibraryServicePane.prototype._processListsInLibrary =
  904. function sbLibraryServicePane__processListsInLibrary(aLibrary) {
  905.   //logcall(arguments);
  906.  
  907.   // Listener to receive enumerated items and store then in an array
  908.   var listener = {
  909.     items: [],
  910.     onEnumerationBegin: function() { },
  911.     onEnumerationEnd: function() { },
  912.     onEnumeratedItem: function(list, item) {
  913.       this.items.push(item);
  914.     }
  915.   };
  916.  
  917.   // Enumerate all lists in this library
  918.   aLibrary.enumerateItemsByProperty(SBProperties.isList, "1",
  919.                                     listener );
  920.  
  921.   // Make sure we have a node for each list
  922.   for (var i = 0; i < listener.items.length; i++) {
  923.     this._ensureMediaListNodeExists(listener.items[i]);
  924.   }
  925. }
  926.  
  927.  
  928. /**
  929.  * The given library has been added.  Show it in the service pane.
  930.  */
  931. sbLibraryServicePane.prototype._libraryAdded =
  932. function sbLibraryServicePane__libraryAdded(aLibrary) {
  933.   //logcall(arguments);
  934.   var node = this._ensureLibraryNodeExists(aLibrary);
  935.  
  936.   // Listen to changes in the library so that we can display new playlists
  937.   var filter = SBProperties.createArray([[SBProperties.mediaListName, null]]);
  938.   aLibrary.addListener(this,
  939.                        false,
  940.                        Ci.sbIMediaList.LISTENER_FLAGS_ALL &
  941.                          ~Ci.sbIMediaList.LISTENER_FLAGS_AFTERITEMREMOVED,
  942.                        filter);
  943.  
  944.   this._processListsInLibrary(aLibrary);
  945.  
  946.   return node;
  947. }
  948.  
  949.  
  950. /**
  951.  * The given library has been removed.  Just hide the contents
  952.  * rather than deleting so that if it is ever reattached
  953.  * we will remember any ordering (drag-drop) information
  954.  */
  955. sbLibraryServicePane.prototype._libraryRemoved =
  956. function sbLibraryServicePane__libraryRemoved(aLibrary) {
  957.   //logcall(arguments);
  958.  
  959.   // Find the node for this library
  960.   var id = this._libraryURN(aLibrary);
  961.   var node = this._servicePane.getNode(id);
  962.  
  963.   // Hide this node and everything below it
  964.   if (node) {
  965.     this._hideLibraryNodes(node);
  966.   }
  967.  
  968.   aLibrary.removeListener(this);
  969. }
  970.  
  971. sbLibraryServicePane.prototype._refreshLibraryNodes =
  972. function sbLibraryServicePane__refreshLibraryNodes(aLibrary) {
  973.   var id = this._libraryURN(aLibrary);
  974.   var node = this._servicePane.getNode(id);
  975.   this._scanForRemovedItems(aLibrary);
  976.   this._ensureLibraryNodeExists(aLibrary);
  977.   this._processListsInLibrary(aLibrary);
  978. }
  979.  
  980. /**
  981.  * The given media list has been added. Show it in the service pane.
  982.  */
  983. sbLibraryServicePane.prototype._playlistAdded =
  984. function sbLibraryServicePane__playlistAdded(aMediaList) {
  985.   //logcall(arguments);
  986.   this._ensureMediaListNodeExists(aMediaList);
  987. }
  988.  
  989.  
  990. /**
  991.  * The given media list has been removed. Delete the node, as it
  992.  * is unlikely that the same playlist will come back again.
  993.  */
  994. sbLibraryServicePane.prototype._playlistRemoved =
  995. function sbLibraryServicePane__playlistRemoved(aMediaList) {
  996.   //logcall(arguments);
  997.  
  998.   var id = this._itemURN(aMediaList);
  999.   var node = this._servicePane.getNode(id);
  1000.   if (node) {
  1001.     this._servicePane.removeNode(node);
  1002.   }
  1003. }
  1004.  
  1005.  
  1006. /**
  1007.  * The given media list has been updated.
  1008.  * The name and other properties may have changed.
  1009.  */
  1010. sbLibraryServicePane.prototype._mediaListUpdated =
  1011. function sbLibraryServicePane__mediaListUpdated(aMediaList) {
  1012.   //logcall(arguments);
  1013.   if (aMediaList instanceof Ci.sbILibrary) {
  1014.     this._ensureLibraryNodeExists(aMediaList);
  1015.   } else if (aMediaList instanceof Ci.sbIMediaList) {
  1016.     this._ensureMediaListNodeExists(aMediaList);
  1017.   }
  1018. }
  1019.  
  1020.  
  1021. /**
  1022.  * Get a service pane identifier for the given media item
  1023.  */
  1024. sbLibraryServicePane.prototype._itemURN =
  1025. function sbLibraryServicePane__itemURN(aMediaItem) {
  1026.   return URN_PREFIX_ITEM + aMediaItem.guid;
  1027. }
  1028.  
  1029.  
  1030. /**
  1031.  * Get a service pane identifier for the given library
  1032.  */
  1033. sbLibraryServicePane.prototype._libraryURN =
  1034. function sbLibraryServicePane__libraryURN(aLibrary) {
  1035.   return URN_PREFIX_LIBRARY + aLibrary.guid;
  1036. }
  1037.  
  1038.  
  1039. /**
  1040.  * Given a resource id, attempt to extract the GUID of a media item.
  1041.  */
  1042. sbLibraryServicePane.prototype._getItemGUIDForURN =
  1043. function sbLibraryServicePane__getItemGUIDForURN(aID) {
  1044.   //logcall(arguments);
  1045.   var index = aID.indexOf(URN_PREFIX_ITEM);
  1046.   if (index >= 0) {
  1047.     return aID.slice(URN_PREFIX_ITEM.length);
  1048.   }
  1049.   return null;
  1050. }
  1051.  
  1052.  
  1053. /**
  1054.  * Given a resource id, attempt to extract the GUID of a library.
  1055.  */
  1056. sbLibraryServicePane.prototype._getLibraryGUIDForURN =
  1057. function sbLibraryServicePane__getLibraryGUIDForURN(aID) {
  1058.   //logcall(arguments);
  1059.   var index = aID.indexOf(URN_PREFIX_LIBRARY);
  1060.   if (index >= 0) {
  1061.     return aID.slice(URN_PREFIX_LIBRARY.length);
  1062.   }
  1063.   return null;
  1064. }
  1065.  
  1066.  
  1067. /**
  1068.  * Given a resource id, attempt to get an sbIMediaItem.
  1069.  * This is the inverse of _itemURN
  1070.  */
  1071. sbLibraryServicePane.prototype._getItemForURN =
  1072. function sbLibraryServicePane__getItemForURN(aID) {
  1073.   //logcall(arguments);
  1074.   var guid = this._getItemGUIDForURN(aID);
  1075.   if (guid) {
  1076.     var node = this._servicePane.getNode(aID);
  1077.     var libraryGUID = node.getAttributeNS(LSP, "LibraryGUID");
  1078.     if (libraryGUID) {
  1079.       try {
  1080.         var library = this._libraryManager.getLibrary(libraryGUID);
  1081.         return library.getMediaItem(guid);
  1082.       } catch (e) {
  1083.         dump("sbLibraryServicePane__getItemForURN: error trying to get medialist " +
  1084.              guid + " from library " + libraryGUID + "\n");
  1085.       }
  1086.     }
  1087.  
  1088.     // URNs of visible nodes in the servicetree should always refer
  1089.     // to an existing media item...
  1090.     dump("sbLibraryServicePane__getItemForURN: could not find a mediaItem " +
  1091.          "for URN " + aID + ". The service pane must be out of sync with " +
  1092.          "the libraries!\n");
  1093.   }
  1094.   return null;
  1095. }
  1096.  
  1097.  
  1098. /**
  1099.  * Given a resource id, attempt to get an sbILibrary.
  1100.  * This is the inverse of _libraryURN
  1101.  */
  1102. sbLibraryServicePane.prototype._getLibraryForURN =
  1103. function sbLibraryServicePane__getLibraryForURN(aID) {
  1104.   //logcall(arguments);
  1105.   var guid = this._getLibraryGUIDForURN(aID);
  1106.   if (guid) {
  1107.     return this._libraryManager.getLibrary(guid);
  1108.   }
  1109.   return null;
  1110. }
  1111.  
  1112.  
  1113.  
  1114. /**
  1115.  * Get the service pane node for the given library,
  1116.  * creating one if none exists.
  1117.  */
  1118. sbLibraryServicePane.prototype._ensureLibraryNodeExists =
  1119. function sbLibraryServicePane__ensureLibraryNodeExists(aLibrary) {
  1120.   //logcall(arguments);
  1121.  
  1122.   // Get the Node.
  1123.   var id = this._libraryURN(aLibrary);
  1124.   var node = this._servicePane.getNode(id);
  1125.   var newnode = false;
  1126.   if (!node) {
  1127.     // Create the node
  1128.     node = this._servicePane.addNode(id, this._servicePane.root, true);
  1129.     newnode = true;
  1130.   }
  1131.  
  1132.   var customType = aLibrary.getProperty(SBProperties.customType);
  1133.  
  1134.   // Refresh the information just in case it is supposed to change
  1135.   // Don't set name if it hasn't changed to avoid a UI redraw
  1136.   if (node.name != aLibrary.name) {
  1137.     node.name = aLibrary.name;
  1138.   }
  1139.   node.url = null;
  1140.   node.contractid = CONTRACTID;
  1141.   node.editable = false;
  1142.   node.hidden = (aLibrary.getProperty(SBProperties.hidden) == "1");
  1143.  
  1144.   if (aLibrary == this._libraryManager.mainLibrary) {
  1145.     // the main library uses a separate Playlists folder
  1146.     this._ensurePlaylistFolderExists();
  1147.     // Set the weight of the main library
  1148.     node.setAttributeNS(SP, 'Weight', -4);
  1149.   } if (customType == 'web') {
  1150.     // Set the weight of the web library
  1151.     node.setAttributeNS(SP, 'Weight', 5);
  1152.     node.hidden = true;
  1153.   } else {
  1154.     // other libraries store the playlists under them, but only
  1155.     // assign the default value if they do not specifically tell
  1156.     // us not to do so
  1157.     
  1158.     if (node.getAttributeNS(SP,'dndCustomAccept') != 'true')
  1159.       node.dndAcceptIn = 'text/x-sb-playlist-'+aLibrary.guid;
  1160.   }
  1161.   // Set properties for styling purposes
  1162.   this._mergeProperties(node,
  1163.                         ["library",
  1164.                          "libraryguid-" + aLibrary.guid,
  1165.                          aLibrary.type,
  1166.                          customType]);
  1167.   // Save the type of media list so that we can group by type
  1168.   node.setAttributeNS(LSP, "ListType", aLibrary.type)
  1169.   // Save the guid of the library
  1170.   node.setAttributeNS(LSP, "LibraryGUID", aLibrary.guid);
  1171.   // and save it as the list guid
  1172.   node.setAttributeNS(LSP, "ListGUID", aLibrary.guid);
  1173.   // Save the customType for use by metrics.
  1174.   node.setAttributeNS(LSP, "ListCustomType", customType);
  1175.   // Save the customType for use by metrics.
  1176.   node.setAttributeNS(LSP, "LibraryCustomType", customType);
  1177.  
  1178.   if (newnode) {
  1179.     // Position the node in the tree
  1180.     this._insertLibraryNode(node, aLibrary);
  1181.   }
  1182.  
  1183.   return node;
  1184. }
  1185.  
  1186.  
  1187. /**
  1188.  * Get the service pane node for the given media list,
  1189.  * creating one if none exists.
  1190.  */
  1191. sbLibraryServicePane.prototype._ensureMediaListNodeExists =
  1192. function sbLibraryServicePane__ensureMediaListNodeExists(aMediaList) {
  1193.   //logcall(arguments);
  1194.  
  1195.   var id = this._itemURN(aMediaList);
  1196.   var node = this._servicePane.getNode(id);
  1197.   var newnode = false;
  1198.   if (!node) {
  1199.     // Create the node
  1200.     // NOTE: it's a container for drag and drop purposes only.
  1201.     node = this._servicePane.addNode(id, this._servicePane.root, true);
  1202.     newnode = true;
  1203.   }
  1204.  
  1205.   var customType = aMediaList.getProperty(SBProperties.customType);
  1206.   var libCustomType = aMediaList.library.getProperty(SBProperties.customType);
  1207.  
  1208.   // Refresh the information just in case it is supposed to change
  1209.   // Don't set name if it hasn't changed to avoid a UI redraw
  1210.   if (node.name != aMediaList.name) {
  1211.     node.name = aMediaList.name;
  1212.   }
  1213.   node.url = null;
  1214.   node.contractid = CONTRACTID;
  1215.   if (customType == 'download') {
  1216.     // the download media list isn't editable
  1217.     node.editable = false;
  1218.     // set the weight of the downloads list
  1219.     node.setAttributeNS(SP, 'Weight', -3);
  1220.   } else {
  1221.     // the rest are, but only if the items themselves are not readonly
  1222.     node.editable = aMediaList.userEditable;
  1223.   }
  1224.   // Set properties for styling purposes
  1225.   if (aMediaList.getProperty("http://songbirdnest.com/data/1.0#isSubscription") == "1") {
  1226.     this._mergeProperties(node, ["medialist", "medialisttype-dynamic"]);
  1227.     node.setAttributeNS(LSP, "ListSubscription", "1");
  1228.   } else {
  1229.     this._mergeProperties(node, ["medialist medialisttype-" + aMediaList.type]);
  1230.     node.setAttributeNS(LSP, "ListSubscription", "0");
  1231.   }
  1232.   // Add the customType to the properties to encourage people to set it for CSS
  1233.   this._mergeProperties(node, [customType]);
  1234.   // Save the type of media list so that we can group by type
  1235.   node.setAttributeNS(LSP, "ListType", aMediaList.type);
  1236.   // Save the guid of the library that owns this media list
  1237.   node.setAttributeNS(LSP, "LibraryGUID", aMediaList.library.guid);
  1238.   // and the guid of this list
  1239.   node.setAttributeNS(LSP, "ListGUID", aMediaList.guid);
  1240.   // Save the parent library custom type for this list.
  1241.   node.setAttributeNS(LSP, "LibraryCustomType", libCustomType);
  1242.   // Save the list customType for use by metrics.
  1243.   node.setAttributeNS(LSP, "ListCustomType", customType);
  1244.  
  1245.   // if auto dndAcceptIn/Near hasn't been disabled, assign it now
  1246.   if (node.getAttributeNS(SP,'dndCustomAccept') != 'true') {
  1247.     if (aMediaList.library == this._libraryManager.mainLibrary) {
  1248.       // a playlist in the main library is considered a toplevel node
  1249.       if (customType == 'download') {
  1250.         // unless its the download playlist
  1251.         node.dndDragTypes = '';
  1252.         node.dndAcceptNear = '';
  1253.       } else {
  1254.         node.dndDragTypes = 'text/x-sb-playlist';
  1255.         node.dndAcceptNear = 'text/x-sb-playlist';
  1256.       }
  1257.     } else {
  1258.       // playlists in other libraries can only go into their libraries' nodes
  1259.       node.dndDragTypes = 'text/x-sb-playlist-'+aMediaList.library.guid;
  1260.       node.dndAcceptNear = 'text/x-sb-playlist-'+aMediaList.library.guid;
  1261.     }
  1262.   }
  1263.  
  1264.   if (newnode) {
  1265.     // Place the node in the tree
  1266.     this._insertMediaListNode(node, aMediaList);
  1267.   }
  1268.  
  1269.   // Get hidden state from list, since we hide all list nodes on startup
  1270.   node.hidden = (aMediaList.getProperty(SBProperties.hidden) == "1");
  1271.  
  1272.   return node;
  1273. }
  1274.  
  1275. /**
  1276.  * Get the service pane node for the Playlists folder (which contains all
  1277.  * the playlists in the main library).
  1278.  */
  1279. sbLibraryServicePane.prototype._ensurePlaylistFolderExists =
  1280. function sbLibraryServicePane__ensurePlaylistFolderExists() {
  1281.   var fnode = this._servicePane.getNode('SB:Playlists');
  1282.   if (!fnode) {
  1283.     // make sure it exists
  1284.     var fnode = this._servicePane.addNode('SB:Playlists',
  1285.         this._servicePane.root, true);
  1286.   }
  1287.   fnode.name = '&servicesource.playlists';
  1288.   this._mergeProperties(fnode, ["folder", this._makeCSSProperty(fnode.name)]);
  1289.   fnode.hidden = false;
  1290.   fnode.contractid = CONTRACTID;
  1291.   fnode.dndAcceptIn = 'text/x-sb-playlist';
  1292.   fnode.editable = false;
  1293.   fnode.setAttributeNS(SP, 'Weight', 3);
  1294.   return fnode;
  1295. }
  1296.  
  1297.  
  1298. sbLibraryServicePane.prototype._scanForRemovedItems =
  1299. function sbLibraryServicePane__scanForRemovedItems(aLibrary) {
  1300.   // Get the list of nodes for items within the library
  1301.   var libraryItemNodeList = this._servicePane.getNodesByAttributeNS
  1302.                                                 (LSP,
  1303.                                                  "LibraryGUID",
  1304.                                                  aLibrary.guid);
  1305.  
  1306.   // Remove nodes whose items no longer exist
  1307.   var libraryItemNodeEnum = libraryItemNodeList.enumerate();
  1308.   while (libraryItemNodeEnum.hasMoreElements()) {
  1309.     // Get the library item node
  1310.     libraryItemNode =
  1311.       libraryItemNodeEnum.getNext().QueryInterface(Ci.sbIServicePaneNode);
  1312.  
  1313.     // Skip library nodes
  1314.     if (this._nodeIsLibrary(libraryItemNode))
  1315.       continue;
  1316.  
  1317.     // Remove node if item no longer exists
  1318.     var mediaItem = this._getItemForURN(libraryItemNode.id);
  1319.     if (!mediaItem)
  1320.       this._servicePane.removeNode(libraryItemNode);
  1321.   }
  1322. }
  1323.  
  1324.  
  1325. /**
  1326.  * Logic to determine where a library node should appear
  1327.  * in the service pane tree
  1328.  */
  1329. sbLibraryServicePane.prototype._insertLibraryNode =
  1330. function sbLibraryServicePane__insertLibraryNode(aNode, aLibrary) {
  1331.   //logcall(arguments);
  1332.  
  1333.   // If this is the main library it should go at
  1334.   // the top of the list
  1335.   if (aLibrary == this._libraryManager.mainLibrary) {
  1336.     if (this._servicePane.root.firstChild &&
  1337.         this._servicePane.root.firstChild.id != aNode.id)
  1338.     {
  1339.       this._servicePane.root.insertBefore(aNode,
  1340.               this._servicePane.root.firstChild);
  1341.     }
  1342.   // Otherwise, add above the first non-library item?
  1343.   } else {
  1344.     this._insertAfterLastOfSameType(aNode, this._servicePane.root);
  1345.   }
  1346. }
  1347.  
  1348. /**
  1349.  * Logic to determine where a media list node should appear
  1350.  * in the service pane tree
  1351.  */
  1352. sbLibraryServicePane.prototype._insertNodeAfter =
  1353. function sbLibraryServicePane__insertNodeAfter(aParent, aNode, aPrecedingNode) {
  1354.   // don't be bad
  1355.   if (!aParent.isContainer) {
  1356.     dump("sbLibraryServicePane__insertNodeAfter: ");
  1357.     dump("cannot insert under non-container node.\n");
  1358.     return;
  1359.   }
  1360.   aParent.isOpen = true;
  1361.  
  1362.   // the node we'll use to insertBefore
  1363.   var next;
  1364.   // if the preceding node is not null, use its next sibling, otherwise
  1365.   // use the parent first child
  1366.   if (aPrecedingNode)
  1367.     next = aPrecedingNode.nextSibling;
  1368.   else
  1369.     next = aParent.firstChild;
  1370.     
  1371.   if (next) {
  1372.     // if the next node is same node as the one we are inserting, 
  1373.     // there is nothing to do
  1374.     if (next.id != aNode.id) {
  1375.       aParent.insertBefore(aNode, next);
  1376.     }
  1377.   } else {
  1378.     // there is no next item, the parent has no item at all, so use
  1379.     // append instead
  1380.     aParent.appendChild(aNode);
  1381.   }
  1382. }
  1383.  
  1384. /**
  1385.  * Logic to determine where a media list node should appear
  1386.  * in the service pane tree
  1387.  */
  1388. sbLibraryServicePane.prototype._insertMediaListNode =
  1389. function sbLibraryServicePane__insertMediaListNode(aNode, aMediaList) {
  1390.   //logcall(arguments);
  1391.  
  1392.   // If it is a main library media list, it belongs in the "Playlists" folder
  1393.   if (aMediaList.library == this._libraryManager.mainLibrary)
  1394.   {
  1395.     // unless it's the download playlist
  1396.     if (aNode.getAttributeNS(LSP, 'ListCustomType') == 'download') {
  1397.       // FIXME: put it right after the library
  1398.       this._servicePane.root.appendChild(aNode);
  1399.     } else {
  1400.       var self = this;
  1401.       // this describes each type of playlist with the attributes/value pairs
  1402.       // that we can use to recognize it.
  1403.       // Note that the "type" field should be unique. it is not used anywhere
  1404.       // else than in this function, so you may use whatever you wish.
  1405.       var type_smart =        { type: "smart",
  1406.                                 conditions: [ { attribute: "ListType",
  1407.                                                 value    : "smart" } ]
  1408.                               };
  1409.       var type_simple =       { type: "simple",
  1410.                                 conditions: [ { attribute: "ListType",
  1411.                                                 value    : "simple" },
  1412.                                               { attribute: "ListSubscription",
  1413.                                                 value    : "0" } ]
  1414.                               };
  1415.       var type_subscription = { type: "subscription",
  1416.                                 conditions: [ { attribute: "ListType",
  1417.                                                 value    : "simple" },
  1418.                                               { attribute: "ListSubscription",
  1419.                                                 value    : "1" } ]
  1420.                               };
  1421.       
  1422.       // this is the order in which the playlists appear in the playlist folder
  1423.       var playlistTypes = [ type_smart, 
  1424.                             type_simple, 
  1425.                             type_subscription ];
  1426.       
  1427.       // create a search array to be used in _findLastNodeByAttributes
  1428.       // based on the type provided. If the type is not found, the search
  1429.       // array will contain all the types, so the list will go to the end
  1430.       // of the folder.
  1431.       function makeSearchArray(aPlaylistType) {
  1432.         var searchArray = [];
  1433.         for (var i=0;i<playlistTypes.length;i++) {
  1434.           var typeObject = playlistTypes[i];
  1435.           searchArray.push(typeObject.conditions);
  1436.           if (typeObject.type == aPlaylistType)
  1437.             break;
  1438.         }
  1439.         return searchArray;
  1440.       }
  1441.       
  1442.       // returns the playlist type for the given node
  1443.       function getNodeType(aNode) {
  1444.         for (var i=0;i<playlistTypes.length;i++) {
  1445.           var typeObject = playlistTypes[i];
  1446.           if (self._matchNode(aNode, typeObject.conditions))
  1447.             return typeObject.type;
  1448.         }
  1449.       }
  1450.  
  1451.       // make sure the playlist folder exists
  1452.       var folder = this._ensurePlaylistFolderExists();
  1453.       
  1454.       // get the type for this node
  1455.       var nodeType = getNodeType(aNode);
  1456.       
  1457.       // construct the search array
  1458.       var searchArray = makeSearchArray(nodeType);
  1459.  
  1460.       // look for the insertion point
  1461.       var insertionPoint = this._findLastNodeByAttributes(folder, 
  1462.                                                           searchArray,
  1463.                                                           aNode);
  1464.       
  1465.       // insert at the right spot (if insertionPoint is null, the node will
  1466.       // be inserted as the new first child)
  1467.       this._insertNodeAfter(folder, aNode, insertionPoint);
  1468.     }
  1469.   }
  1470.   // If it is a secondary library playlist, it should be
  1471.   // added as a child of that library
  1472.   else
  1473.   {
  1474.     // Find the parent libary in the tree
  1475.     var parentLibraryNode = this.getNodeForLibraryResource(aMediaList.library);
  1476.  
  1477.     // If the parent library is a top level node, and a container, 
  1478.     // make the playlist node its child
  1479.     
  1480.     if (parentLibraryNode) {
  1481.       if (parentLibraryNode.isContainer && 
  1482.           parentLibraryNode.parentNode.id == "SB:Root") {
  1483.         this._insertAfterLastOfSameType(aNode, parentLibraryNode);
  1484.       } else {
  1485.         this._insertAfterLastOfSameType(aNode, parentLibraryNode.parentNode);
  1486.       }
  1487.     } else {
  1488.       dump("sbLibraryServicePane__insertMediaListNode: ");
  1489.       dump("could not add media list to parent library ");
  1490.       if (parentLibraryNode)
  1491.           dump(parentLibraryNode.name + "\n");
  1492.       else
  1493.           dump("\n");
  1494.       this._servicePane.root.appendChild(aNode);
  1495.     }
  1496.   }
  1497. }
  1498.  
  1499. /**
  1500.  * Match a node to a list of attribute/value pairs. All of the values for a 
  1501.  * node should match for this function to return true.
  1502.  */
  1503. sbLibraryServicePane.prototype._matchNode =
  1504. function sbLibraryServicePane__matchNode(aNode, aAttributeValues) {
  1505.   // it's a match if there are no attribute/value pairs in the list
  1506.   var match = true;
  1507.   
  1508.   // match each of the attribute/value pairs
  1509.   for each (var match in aAttributeValues) {
  1510.     var attribute = match.attribute;
  1511.     var value = match.value;
  1512.     // if this is not a match, skip this condition
  1513.     if (aNode.getAttributeNS(LSP, attribute) != value) {
  1514.       match = false;
  1515.       // don't test any more attribute/value pairs
  1516.       break;
  1517.     }    
  1518.   }
  1519.   
  1520.   return match;
  1521. }
  1522.  
  1523. /**
  1524.  * Finds the last node that matches any of the search criterions in a list,
  1525.  * with an optional excluded node.
  1526.  * Each search criterion is a list of attribute/pairs that must all match a node
  1527.  * for the condition to be fulfilled.
  1528.  */
  1529. sbLibraryServicePane.prototype._findLastNodeByAttributes =
  1530. function sbLibraryServicePane__findLastNodeByAttributes(aParent, 
  1531.                                                         aSearchArray,
  1532.                                                         aExcludedNode) {
  1533.   var children = aParent.childNodes;
  1534.   var lastMatch;
  1535.   while (children.hasMoreElements()) {
  1536.     var child = children.getNext().QueryInterface(Ci.sbIServicePaneNode);
  1537.     
  1538.     if (child.hidden)
  1539.       continue;
  1540.  
  1541.     // If this node belongs to the library service pane, and
  1542.     // is not the node we are trying to insert, then check
  1543.     // to see if this is a match
  1544.     if (child.contractid == CONTRACTID && 
  1545.         (!aExcludedNode || 
  1546.           child.id != aExcludedNode.id)) {
  1547.     
  1548.       // check each of the search criteria
  1549.       for each (var criterion in aSearchArray) {
  1550.         if (this._matchNode(child, criterion)) {
  1551.           // remember this node
  1552.           lastMatch = child;
  1553.           // don't test any more criteria
  1554.           break;
  1555.         }
  1556.       }
  1557.     }
  1558.   } // end of while
  1559.   return lastMatch;
  1560. }
  1561.  
  1562.  
  1563. /**
  1564.  * Inserts the given node under the given parent and attempts to keep
  1565.  * children grouped by type.  The node is inserted at the end of the list
  1566.  * or next to the last child of the same type as the given node.
  1567.  */
  1568. sbLibraryServicePane.prototype._insertAfterLastOfSameType =
  1569. function sbLibraryServicePane__insertAfterLastOfSameType(aNode, aParent) {
  1570.   //logcall(arguments);
  1571.  
  1572.   if (!aParent.isContainer) {
  1573.     dump("sbLibraryServicePane__insertAfterLastOfSameType: ");
  1574.     dump("cannot insert under non-container node.\n");
  1575.     return;
  1576.   }
  1577.   aParent.isOpen = true;
  1578.  
  1579.   // Insert after last of same type before hitting
  1580.   // non-library related items
  1581.   var children = aParent.childNodes;
  1582.   var nodeType = aNode.getAttributeNS(LSP, "ListType");
  1583.   var lastOfSameType;
  1584.   var inserted = false;
  1585.   while (children.hasMoreElements()) {
  1586.     var child = children.getNext().QueryInterface(Ci.sbIServicePaneNode);
  1587.  
  1588.     // If this node belongs to the library service pane, and
  1589.     // is not the node we are trying to insert, then check
  1590.     // to see if this is an insertion point candidate
  1591.     if (child.contractid == CONTRACTID && child.id != aNode.id) {
  1592.  
  1593.       // Keep track of last node similar to this one, preferring
  1594.       // nodes that are exactly the same type as this one
  1595.       if (nodeType == child.getAttributeNS(LSP, "ListType") ||
  1596.           lastOfSameType == null ||
  1597.           (lastOfSameType != null &&
  1598.              lastOfSameType.getAttributeNS(LSP, "ListType") != nodeType))
  1599.       {
  1600.         lastOfSameType = child;
  1601.       }
  1602.     }
  1603.   } // end of while
  1604.  
  1605.   // Insert the new list after the last of the same type
  1606.   // or at the end of the list
  1607.   if (lastOfSameType && lastOfSameType.nextSibling) {
  1608.     if (lastOfSameType.nextSibling.id != aNode.id) {
  1609.       aParent.insertBefore(aNode, lastOfSameType.nextSibling);
  1610.     }
  1611.   } else {
  1612.     aParent.appendChild(aNode);
  1613.   }
  1614. }
  1615.  
  1616. sbLibraryServicePane.prototype._appendMenuItem =
  1617. function sbLibraryServicePane__appendMenuItem(aContextMenu, aLabel, aCallback) {
  1618.   var item = aContextMenu.ownerDocument.createElement("menuitem");
  1619.   item.setAttribute("label", aLabel);
  1620.   item.addEventListener("command", aCallback, false);
  1621.   aContextMenu.appendChild(item);
  1622. }
  1623.  
  1624. sbLibraryServicePane.prototype._appendCommands =
  1625. function sbLibraryServicePane__appendCommands(aContextMenu, aList, aParentWindow) {
  1626.   if (this._lastMenuitems && this._lastMenuitems.destroy) {
  1627.     var pnode = this._lastMenuitems.parentNode;
  1628.     this._lastMenuitems.destroy();
  1629.     this._lastMenuitems = null;
  1630.   }
  1631.   var itemBuilder = aContextMenu.ownerDocument.createElement("sb-commands-menuitems");
  1632.   itemBuilder.setAttribute("id", "playlist-commands");
  1633.   itemBuilder.setAttribute("commandtype", "medialist");
  1634.   itemBuilder.setAttribute("bind", aList.library.guid + ';' + aList.guid);
  1635.   aContextMenu.appendChild(itemBuilder);
  1636.   this._lastMenuitems = itemBuilder;
  1637. }
  1638.  
  1639. /**
  1640.  * This function is a recursive helper for onListCleared (below) that will
  1641.  * remove all the playlist nodes for a given library.
  1642.  */
  1643. sbLibraryServicePane.prototype._removeListNodesForLibrary =
  1644. function sbLibraryServicePane__removeListNodesForLibrary(aStartNode, aLibraryGUID) {
  1645.  
  1646.   var node = aStartNode.firstChild;
  1647.  
  1648.   while (node) {
  1649.  
  1650.     if (node.isContainer) {
  1651.       this._removeListNodesForLibrary(node, aLibraryGUID);
  1652.     }
  1653.  
  1654.     var nextSibling = node.nextSibling;
  1655.  
  1656.     if (this._getItemGUIDForURN(node.id)) {
  1657.       var nodeLibraryGUID = node.getAttributeNS(LSP, "LibraryGUID");
  1658.       if (nodeLibraryGUID == aLibraryGUID) {
  1659.         this._servicePane.removeNode(node);
  1660.       }
  1661.     }
  1662.  
  1663.     node = nextSibling;
  1664.   }
  1665. }
  1666.  
  1667. sbLibraryServicePane.prototype._mergeProperties =
  1668. function sbLibraryServicePane__mergeProperties(aNode, aList) {
  1669.  
  1670.   var o = {};
  1671.   var a = aNode.properties?aNode.properties.split(" "):[];
  1672.   a.forEach(function(prop) {
  1673.     o[prop] = 1;
  1674.   });
  1675.  
  1676.   aList.forEach(function(prop) {
  1677.     o[prop] = 1;
  1678.   });
  1679.  
  1680.   var retval = [];
  1681.   for (var prop in o) {
  1682.     retval.push(prop);
  1683.   }
  1684.   aNode.properties = retval.join(" ");
  1685. }
  1686.  
  1687. /**
  1688.  * Turn a partial entity (&foo.bar) into a css property string (foo-bar),
  1689.  * but leaves other strings as they are.
  1690.  */
  1691. sbLibraryServicePane.prototype._makeCSSProperty = 
  1692. function sbLibraryServicePane__makeCSSProperty(aString) {
  1693.   if ( aString[0] == "&" ) {
  1694.     aString = aString.substr(1, aString.length);
  1695.     aString = aString.replace(/\./g, "-");
  1696.   }
  1697.   return aString;
  1698. }
  1699.  
  1700. ///////////////////////////////
  1701. // sbILibraryManagerListener //
  1702. ///////////////////////////////
  1703.  
  1704. sbLibraryServicePane.prototype.onLibraryRegistered =
  1705. function sbLibraryServicePane_onLibraryRegistered(aLibrary) {
  1706.   //logcall(arguments);
  1707.   this._libraryAdded(aLibrary);
  1708. }
  1709. sbLibraryServicePane.prototype.onLibraryUnregistered =
  1710. function sbLibraryServicePane_onLibraryUnregistered(aLibrary) {
  1711.   //logcall(arguments);
  1712.   this._libraryRemoved(aLibrary);
  1713. }
  1714.  
  1715. //////////////////////////
  1716. // sbIMediaListListener //
  1717. //////////////////////////
  1718.  
  1719. sbLibraryServicePane.prototype.onItemAdded =
  1720. function sbLibraryServicePane_onItemAdded(aMediaList, aMediaItem, aIndex) {
  1721.   //logcall(arguments);
  1722.   if (this._batch.isActive()) {
  1723.     // We are going to refresh all the nodes once we exit the batch so
  1724.     // we don't need any more of these notifications
  1725.     this._refreshPending = true;
  1726.     return true;
  1727.   }
  1728.   else {
  1729.     var isList = aMediaItem instanceof Ci.sbIMediaList;
  1730.     if (isList) {
  1731.       this._playlistAdded(aMediaItem);
  1732.     }
  1733.     return false;
  1734.   }
  1735. }
  1736. sbLibraryServicePane.prototype.onBeforeItemRemoved =
  1737. function sbLibraryServicePane_onBeforeItemRemoved(aMediaList, aMediaItem, aIndex) {
  1738.   //logcall(arguments);
  1739.   if (this._batch.isActive()) {
  1740.     // We are going to refresh all the nodes once we exit the batch so
  1741.     // we don't need any more of these notifications
  1742.     this._refreshPending = true;
  1743.     return true;
  1744.   }
  1745.   else {
  1746.     var isList = aMediaItem instanceof Ci.sbIMediaList;
  1747.     if (isList) {
  1748.       this._playlistRemoved(aMediaItem);
  1749.     }
  1750.     return false;
  1751.   }
  1752. }
  1753. sbLibraryServicePane.prototype.onAfterItemRemoved =
  1754. function sbLibraryServicePane_onAfterItemRemoved(aMediaList, aMediaItem, aIndex) {
  1755. }
  1756. sbLibraryServicePane.prototype.onItemUpdated =
  1757. function sbLibraryServicePane_onItemUpdated(aMediaList,
  1758.                                             aMediaItem,
  1759.                                             aProperties) {
  1760.   if (this._batch.isActive()) {
  1761.     // We are going to refresh all the nodes once we exit the batch so
  1762.     // we don't need any more of these notifications
  1763.     this._refreshPending = true;
  1764.     return true;
  1765.   }
  1766.   else {
  1767.     var isList = aMediaItem instanceof Ci.sbIMediaList;
  1768.     if (isList) {
  1769.       this._mediaListUpdated(aMediaItem);
  1770.     }
  1771.     return false;
  1772.   }
  1773. }
  1774. sbLibraryServicePane.prototype.onItemMoved =
  1775. function sbLibraryServicePane_onItemMoved(aMediaList,
  1776.                                           aFromIndex,
  1777.                                           aToIndex) {
  1778. }
  1779. sbLibraryServicePane.prototype.onListCleared =
  1780. function sbLibraryServicePane_onListCleared(aMediaList) {
  1781.   if (this._batch.isActive()) {
  1782.     // We are going to refresh all the nodes once we exit the batch so
  1783.     // we don't need any more of these notifications
  1784.     this._refreshPending = true;
  1785.     return true;
  1786.   }
  1787.   else {
  1788.     if (aMediaList instanceof Ci.sbILibrary) {
  1789.       var libraryGUID = aMediaList.guid;
  1790.  
  1791.       var node = this._servicePane.root;
  1792.       this._removeListNodesForLibrary(node, libraryGUID);
  1793.     }
  1794.     return false;
  1795.   }
  1796. }
  1797. sbLibraryServicePane.prototype.onBatchBegin =
  1798. function sbLibraryServicePane_onBatchBegin(aMediaList) {
  1799.   //logcall(arguments);
  1800.   this._batch.begin();
  1801. }
  1802. sbLibraryServicePane.prototype.onBatchEnd =
  1803. function sbLibraryServicePane_onBatchEnd(aMediaList) {
  1804.   //logcall(arguments);
  1805.   this._batch.end();
  1806.   if (!this._batch.isActive() && this._refreshPending) {
  1807.     this._refreshLibraryNodes(aMediaList);
  1808.     this._refreshPending = false;
  1809.   }
  1810.  
  1811. }
  1812.  
  1813.  
  1814. sbLibraryServicePane.prototype._initLibraryManager =
  1815. function sbLibraryServicePane__initLibraryManager() {
  1816.   // get the library manager
  1817.   this._libraryManager = Cc['@songbirdnest.com/Songbird/library/Manager;1']
  1818.                            .getService(Ci.sbILibraryManager);
  1819.  
  1820.   // register for notifications so that we can keep the service pane
  1821.   // in sync with the the libraries
  1822.   this._libraryManager.addListener(this);
  1823.  
  1824.   this._addAllLibraries();
  1825. }
  1826. /////////////////
  1827. // nsIObserver //
  1828. /////////////////
  1829.  
  1830. sbLibraryServicePane.prototype.observe =
  1831. function sbLibraryServicePane_observe(subject, topic, data) {
  1832.  
  1833.   var obs = Cc["@mozilla.org/observer-service;1"]
  1834.               .getService(Ci.nsIObserverService);
  1835.  
  1836.   if (topic == "songbird-library-manager-ready") {
  1837.     obs.removeObserver(this, "songbird-library-manager-ready");
  1838.  
  1839.     if (!this._libraryManager) {
  1840.       this._initLibraryManager();
  1841.     }
  1842.   }
  1843.   else if (topic == "songbird-library-manager-before-shutdown") {
  1844.     obs.removeObserver(this, "songbird-library-manager-before-shutdown");
  1845.  
  1846.     var libraryManager = Cc['@songbirdnest.com/Songbird/library/Manager;1']
  1847.                            .getService(Ci.sbILibraryManager);
  1848.     libraryManager.removeListener(this);
  1849.   }
  1850. }
  1851.  
  1852. ///////////
  1853. // XPCOM //
  1854. ///////////
  1855.  
  1856. /**
  1857.  * /brief XPCOM initialization code
  1858.  */
  1859. function makeGetModule(CONSTRUCTOR, CID, CLASSNAME, CONTRACTID, CATEGORIES) {
  1860.   return function (comMgr, fileSpec) {
  1861.     return {
  1862.       registerSelf : function (compMgr, fileSpec, location, type) {
  1863.         compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  1864.         compMgr.registerFactoryLocation(CID,
  1865.                         CLASSNAME,
  1866.                         CONTRACTID,
  1867.                         fileSpec,
  1868.                         location,
  1869.                         type);
  1870.         if (CATEGORIES && CATEGORIES.length) {
  1871.           var catman =  Cc["@mozilla.org/categorymanager;1"]
  1872.               .getService(Ci.nsICategoryManager);
  1873.           for (var i=0; i<CATEGORIES.length; i++) {
  1874.             var e = CATEGORIES[i];
  1875.             catman.addCategoryEntry(e.category, e.entry, e.value,
  1876.               true, true);
  1877.           }
  1878.         }
  1879.       },
  1880.  
  1881.       getClassObject : function (compMgr, cid, iid) {
  1882.         if (!cid.equals(CID)) {
  1883.           throw Cr.NS_ERROR_NO_INTERFACE;
  1884.         }
  1885.  
  1886.         if (!iid.equals(Ci.nsIFactory)) {
  1887.           throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  1888.         }
  1889.  
  1890.         return this._factory;
  1891.       },
  1892.  
  1893.       _factory : {
  1894.         createInstance : function (outer, iid) {
  1895.           if (outer != null) {
  1896.             throw Cr.NS_ERROR_NO_AGGREGATION;
  1897.           }
  1898.           return (new CONSTRUCTOR()).QueryInterface(iid);
  1899.         }
  1900.       },
  1901.  
  1902.       unregisterSelf : function (compMgr, location, type) {
  1903.         compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  1904.         compMgr.unregisterFactoryLocation(CID, location);
  1905.         if (CATEGORIES && CATEGORIES.length) {
  1906.           var catman =  Cc["@mozilla.org/categorymanager;1"]
  1907.               .getService(Ci.nsICategoryManager);
  1908.           for (var i=0; i<CATEGORIES.length; i++) {
  1909.             var e = CATEGORIES[i];
  1910.             catman.deleteCategoryEntry(e.category, e.entry, true);
  1911.           }
  1912.         }
  1913.       },
  1914.  
  1915.       canUnload : function (compMgr) {
  1916.         return true;
  1917.       },
  1918.  
  1919.       QueryInterface : function (iid) {
  1920.         if ( !iid.equals(Ci.nsIModule) ||
  1921.              !iid.equals(Ci.nsISupports) )
  1922.           throw Cr.NS_ERROR_NO_INTERFACE;
  1923.         return this;
  1924.       }
  1925.  
  1926.     };
  1927.   }
  1928. }
  1929.  
  1930. var NSGetModule = makeGetModule (
  1931.   sbLibraryServicePane,
  1932.   Components.ID("{64ec2154-3733-4862-af3f-9f2335b14821}"),
  1933.   "Songbird Library Service Pane Service",
  1934.   CONTRACTID,
  1935.   [{
  1936.     category: 'service-pane',
  1937.     entry: '0library', // we want this to load first
  1938.     value: CONTRACTID
  1939.   }]);
  1940.